package cn.yingxd.demo.javatraining.reflectionapp;

import cn.yingxd.demo.javatraining.Main;
import cn.yingxd.demo.javatraining.annotation.RuntimeAnnotation;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class Reflector {
    private void processJarEntry(JarFile jarFile, JarEntry entry, IAnnotationProcessor processor) {
        try (InputStream inputStream = jarFile.getInputStream(entry)) {
            System.out.println("JarEntry: " + entry.getName());
            String className = entry.getName().replace('/', '.').replace(".class", "");
            if (processor!= null) {
                processor.clazz(className);
            }
            ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) {
                @Override
                public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
                    System.out.println("\tAnnotation: " + descriptor + "\n\t\tAnnotationVisible = " + visible);
                    if (processor!= null) {
                        processor.annotation(className, descriptor, visible);
                    }
                    return new AnnotationVisitor(Opcodes.ASM9) {
                        @Override
                        public void visit(String name, Object value) {
                            System.out.println("\t\t" + name + " = " + value);
                            if (processor!= null) {
                                processor.annotationProperty(className, descriptor, visible, name, value);
                            }
                        }
                    };
                }
            };
            ClassReader classReader = new ClassReader(inputStream);
            classReader.accept(classVisitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    private Reflector() {
        System.out.println("Scanner initialized");
    }
    public void processClass(String className, IAnnotationProcessor processor) {
        System.out.println("Class: " + className);
        try {
            Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
            if (processor != null) {
                processor.clazz(className);
            }
            Annotation[] annotations = clazz.getAnnotations();
            for (Annotation annotation : annotations) {
                Class<?> clz = annotation.annotationType();
                System.out.println("\tAnnotation: " + clz.getName() + "\n\t\tAnnotationVisible = true");
                if (processor != null) {
                    processor.annotation(className, clz.getName(), true);
                }
                Method[] methods = clz.getDeclaredMethods();
                for (Method method : methods) {
                    if (method.getDeclaringClass() == Annotation.class)
                        continue;
                    Object value = null;
                    try {
                        value = method.invoke(annotation);
                        System.out.println("\t\t" + method.getName() + " = " + value);
                        if (processor != null) {
                            processor.annotationProperty(className, clz.getName(), true, method.getName(), value);
                        }
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    } catch (InvocationTargetException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
    public void scanJar(String jarFileName, IAnnotationProcessor processor) {
        try (JarFile jarFile = new JarFile(jarFileName)) {
            jarFile.stream()
                    .filter(entry -> entry.getName().endsWith(".class"))
                    .forEach(entry -> processJarEntry(jarFile, entry, processor));
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void scanClass(String className, IAnnotationProcessor processor) {
        System.out.println("Class: " + className);
        if (processor!= null) {
            processor.clazz(className);
        }
        ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) {
            @Override
            public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
                System.out.println("\tAnnotation: " + descriptor + "\n\t\tAnnotationVisible = " + visible);
                if (processor != null) {
                    processor.annotation(className, descriptor, visible);
                }
                return new AnnotationVisitor(Opcodes.ASM9) {
                    @Override
                    public void visit(String name, Object value) {
                        System.out.println("\t\t" + name + " = " + value);
                        if (processor != null) {
                            processor.annotationProperty(className, descriptor, visible, name, value);
                        }
                    }
                };
            }
        };
        String classPath = className.replace('.', '/') + ".class";
        InputStream classStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(classPath);
        try {
            ClassReader classReader = new ClassReader(classStream);
            classReader.accept(classVisitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public interface IAnnotationProcessor {
        void clazz(String className);
        void annotation(String className, String descriptor, boolean visible);
        void annotationProperty(String className, String descriptor, boolean visible, String propName, Object propValue);
    }

    static private Reflector scanner = new Reflector();
    static public Reflector Instance()   {
        return scanner;
    }
}
